﻿using Coding4Fun.MissionControl.API.Models;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;

namespace Coding4Fun.MissionControl.API.Helpers
{
    public class StorageHelper
    {
        /// <summary>
        /// Lists all available sets that are currently stored on the server.
        /// </summary>
        /// <returns>List of sets on the machine.</returns>
        internal static List<Set> GetSets()
        {
            List<Set> sets = null;
            string rawContent = GetRawSets();

            sets = JsonConvert.DeserializeObject<List<Set>>(rawContent);

            return sets;
        }

        /// <summary>
        /// Returns the list of commands that are associated with the given set.
        /// </summary>
        /// <param name="setName">The name of the target set.</param>
        /// <returns>List of commands associated with the given set.</returns>
        internal static List<Command> GetCommands(string setName)
        {
            List<Command> commandList = null;

            var sets = GetSets();

            Set singleSet = null;
            if (sets != null)
                singleSet = (from c in sets where c.Name == setName select c).FirstOrDefault();

            if (singleSet != null)
            {
                commandList = singleSet.Commands;
            }

            return commandList;
        }

        /// <summary>
        /// Gets the list of names for the commands in the requested set.
        /// </summary>
        /// <param name="setName">The name of the target set.</param>
        /// <returns>List of commands associated with the given set.</returns>
        internal static List<string> GetRawCommandNames(string setName)
        {
            List<Command> commandList = GetCommands(setName);
            List<string> stringSet = null;

            if (commandList != null)
            {
                stringSet = commandList.Select(x => x.Name).ToList();
            }

            return stringSet;
        }

        /// <summary>
        /// Get the list of names for all sets on the local server.
        /// </summary>
        /// <returns>List of sets on the machine.</returns>
        internal static List<string> GetRawSetNames()
        {
            List<Set> sets = GetSets();
            List<string> stringSet = null;

            if (sets != null)
            {
                stringSet = sets.Select(x => x.Name).ToList();
            }

            return stringSet;
        }

        /// <summary>
        /// Get the raw string contents of Constants.CORE_CONTAINER_NAME. Should only be used in the
        /// context of this class.
        /// </summary>
        /// <returns>JSON string representing stored sets and commands.</returns>
        internal static string GetRawSets()
        {
            string sets = string.Empty;

            if (File.Exists(Constants.CORE_CONTAINER_NAME))
            {
                using (StreamReader reader = new StreamReader(File.OpenRead(Constants.CORE_CONTAINER_NAME)))
                {
                    sets = reader.ReadToEnd();
                }
            }
            else
            {
                FileStream stream = File.Create(Constants.CORE_CONTAINER_NAME);
                stream.Close();
            }

            return sets;
        }

        /// <summary>
        /// Check whether a set is marked with a IsList flag.
        /// </summary>
        /// <param name="setName">The name of the target set.</param>
        /// <returns>TRUE - set is a list. FALSE - set is not a list.</returns>
        internal static bool IsSetAList(string setName)
        {
            bool isList = true;
            var sets = GetSets();

            Set set = null;

            if (sets != null)
                set = (from c in sets where c.Name == setName select c).FirstOrDefault();

            if (set != null)
                isList = set.IsList;

            return isList;            
        }

        /// <summary>
        /// Serialize the set collection to Constants.CORE_CONTAINER_NAME
        /// </summary>
        /// <param name="sets">Collection to be serialized.</param>
        /// <returns>true if sets are serialized.</returns>
        internal static bool SerializeSets(List<Set> sets)
        {
            try
            {
                using (StreamWriter writer = new StreamWriter(Constants.CORE_CONTAINER_NAME, false))
                {
                    string data = JsonConvert.SerializeObject(sets);
                    writer.Write(data);
                }

                return true;
            }
            catch
            {
                return false;
            }
        }

        /// <summary>
        /// Add a new set to the existing global set collection.
        /// </summary>
        /// <param name="name">Set name.</param>
        /// <returns>true if successfully added set.</returns>
        internal static bool AddSet(string name, bool isList = true)
        {
            var sets = GetSets();

            if (sets == null)
                sets = new List<Set>();

            var singleSet = GetSingleSet(sets, name);

            if (singleSet == null)
                sets.Add(new Set { Name = name, IsList = isList });

            if (SerializeSets(sets))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Retrieves a single set from a collection that has a specific name.
        /// </summary>
        /// <param name="sets">The source collection from which to extract the set.</param>
        /// <param name="name">The name of the set to get.</param>
        /// <returns>An instance of the found set, if any.</returns>
        internal static Set GetSingleSet(List<Set> sets, string name)
        {
            if (sets != null)
                return (from c in sets where c.Name == name select c).FirstOrDefault();
            else
                return null;
        }

        /// <summary>
        /// Add a IR command to an existing set. If the set is not found, it will be created.
        /// </summary>
        /// <param name="command">The command instance to be added.</param>
        /// <param name="targetSet">The name of the target set.</param>
        /// <returns>true if the command was successfully added.</returns>
        internal static bool AddCommand(Command command, string targetSet)
        {
            var sets = GetSets();

            if (sets == null)
                sets = new List<Set>();

            var singleSet = GetSingleSet(sets, targetSet);

            if (singleSet == null)
                singleSet = new Set { Name = targetSet };

            var singleCommand = (from c in singleSet.Commands where c.Name == command.Name select c).FirstOrDefault();
            if (singleCommand == null)
            {
                singleSet.Commands.Add(command);

                if (SerializeSets(sets))
                    return true;
                else
                    return false;
            }
            else
                return false;
        }

        /// <summary>
        /// Retrieve a single command instance from one of the sets on the local server.
        /// </summary>
        /// <param name="commands">Original list of commands.</param>
        /// <param name="name">Name of the command to be retrieved.</param>
        /// <returns>An instance of the command, if found. NULL if not.</returns>
        internal static Command GetSingleCommand(List<Command> commands, string name)
        {
            if (commands != null)
                return (from c in commands where c.Name == name select c).FirstOrDefault();
            else
                return null;
        }

        /// <summary>
        /// Remove a set from a local machine.
        /// </summary>
        /// <param name="sets">Original list of sets.</param>
        /// <param name="targetSet">Name of the set to remove.</param>
        internal static void RemoveSet(List<Set> sets, Set targetSet)
        {
            sets.Remove(targetSet);
            SerializeSets(sets);
        }
    }
}
